package randomwalk.socialgraph;

import java.sql.SQLException;

import odnoklassniki.OdnoklassnikiTrackTrackSubmatrix;
import odnoklassniki.OdnoklassnikiUserTrackSubmatrix;
import odnoklassniki.OdnoklassnikiUserUserSubmatrix;
import lastfm.LastfmTrackTrackSubmatrix;
import lastfm.LastfmUserTrackSubmatrix;
import lastfm.LastfmUserUserSubmatrix;
import musictrackrecommendation.Dataset;
import musictrackrecommendation.ExperimentParameters;
import musictrackrecommendation.Utils;
import randomwalk.PreferencesVector;
import randomwalk.QuizSetVerification;
import randomwalk.Submatrix;
import simpledataset.SimpleMatrix;

/**
 * Social graph: (UU | UTr ) (TrU | TrTr )
 * @uml.dependency   supplier="randomwalk.socialgraph.SocialGraph"
 */
public class CompleteUsersTracksGraph implements SocialGraph {
	private static final int STEP_FOR_CONVERGENCE = 3;

	/**
	 * @uml.property  name="userUser"
	 * @uml.associationEnd  
	 */
	private Submatrix userUser;
	/**
	 * @uml.property  name="userTrack"
	 * @uml.associationEnd  
	 */
	private Submatrix userTrack;
	/**
	 * @uml.property  name="trackTrack"
	 * @uml.associationEnd  
	 */
	private Submatrix trackTrack;

	public CompleteUsersTracksGraph(Submatrix userUser, Submatrix userTrack, Submatrix trackTrack) {
		this.userUser = userUser;
		this.userTrack = userTrack;
		this.trackTrack = trackTrack;
		normalizeSubmatrices();
	}
	
	public CompleteUsersTracksGraph() throws ClassNotFoundException,
			SQLException {
		ExperimentParameters parameters = ExperimentParameters.getInstance();
		int datasetType = parameters.getDatasetType();
		switch (datasetType) {
		case ExperimentParameters.LASTFM_SMALL_DATASET:
			userUser = new LastfmUserUserSubmatrix();			
			userTrack = new LastfmUserTrackSubmatrix();
			trackTrack = new LastfmTrackTrackSubmatrix();
			normalizeSubmatrices();
			break;
		case ExperimentParameters.ODNOKLASSNIKI_DATASET:
			userUser = new OdnoklassnikiUserUserSubmatrix();			
			userTrack = new OdnoklassnikiUserTrackSubmatrix();
			trackTrack = new OdnoklassnikiTrackTrackSubmatrix();

			normalizeSubmatrices();
			break;
		case ExperimentParameters.SIMPLE_DATASET:
			userUser = SimpleMatrix.getUserUser();
			userTrack = SimpleMatrix.getUserTrack();
			normalizeSubmatrices();
			break;
		default:
			userUser = SimpleMatrix.getUserUser();
			userTrack = SimpleMatrix.getUserTrack();
			normalizeSubmatrices();
		}
	}

	@Override
	public void normalizeSubmatrices() {
		ExperimentParameters parameters = ExperimentParameters.getInstance();
		int methodCode = parameters.getNormalizationMethod();

		switch (methodCode) {
		case ExperimentParameters.WHOLE_COLUMN_NORMALIZATION:
			normalizeByColumns();
			break;
		case ExperimentParameters.SEPARATE_COLUMN_NORMALIZATION:
			double userUserNorm = parameters.getUserUserColumnNorm();
			double trackUserNorm = parameters.getTrackUserColumnNorm();
			double userTrackNorm = parameters.getUserTrackColumnNorm();
			double trackTrackNorm = parameters.getTrackTrackColumnNorm();
			normalizeByColumns(userUserNorm, userTrackNorm, trackUserNorm,
					trackTrackNorm);
			break;
		default:
			normalizeByColumns();
			break;
		}
	}

	@Override
	public void normalizeByColumns(double uUNorm, double uTrNorm,
			double trUNorm, double trTrNorm) {
		ExperimentParameters parameters = ExperimentParameters.getInstance();
		Dataset datasetProperties = parameters
				.getDatasetProperties();

		int usersCount = datasetProperties.getUsersMaximalId();
		int tracksCount = datasetProperties.getTracksMaximalId();

		double[] userUserColumnNorms = new double[usersCount + 1];
		double[] trackUserColumnNorms = new double[usersCount + 1];
		double[] userTrackColumnNorms = new double[tracksCount + 1];
		double[] trackTrackColumnNorms = new double[tracksCount + 1];

		// count UserUser elements
		for (int i = 0; i < userUser.getRowIds().length; i++) {
			userUserColumnNorms[userUser.getColumnIds()[i]] += userUser
					.getValues()[i];
		}

		// count TrackTrack elements
		for (int i = 0; i < trackTrack.getRowIds().length; i++) {
			trackTrackColumnNorms[trackTrack.getColumnIds()[i]] += trackTrack
					.getValues()[i];
		}

		for (int i = 0; i < userTrack.getRowIds().length; i++) {
			// count UserTrack elements
			userTrackColumnNorms[userTrack.getColumnIds()[i]] += userTrack
					.getValues()[i];
			// count TrackUser elements
			trackUserColumnNorms[userTrack.getRowIds()[i]] += userTrack
					.getTransponedSubmatrixValues()[i];
		}

		// normalize UserUser elements
		for (int i = 0; i < userUser.getValues().length; i++) {
			userUser.getValues()[i] /= userUserColumnNorms[userUser.getColumnIds()[i]];
			userUser.getValues()[i] *= uUNorm;
		}

		// normalize TrackTrack elements
		for (int i = 0; i < trackTrack.getValues().length; i++) {
			trackTrack.getValues()[i] /= trackTrackColumnNorms[trackTrack
					.getColumnIds()[i]];
			trackTrack.getValues()[i] *= trTrNorm;
		}

		// normalize UserTrack elements
		for (int i = 0; i < userTrack.getValues().length; i++) {
			userTrack.getValues()[i] /=  userTrackColumnNorms[userTrack
					.getColumnIds()[i]];
			userTrack.getValues()[i] *= uTrNorm;
			
			userTrack.getTransponedSubmatrixValues()[i] /= trackUserColumnNorms[userTrack
					.getRowIds()[i]];
			userTrack.getTransponedSubmatrixValues()[i] *= trUNorm;
		}
	}

	@Override
	public void normalizeByColumns() {
		ExperimentParameters parameters = ExperimentParameters.getInstance();
		Dataset datasetProperties = parameters
				.getDatasetProperties();

		int usersCount = datasetProperties.getUsersMaximalId();
		int tracksCount = datasetProperties.getTracksMaximalId();

		double[] userUserColumnNorms = new double[usersCount + 1];
		double[] trackUserColumnNorms = new double[usersCount + 1];
		double[] userTrackColumnNorms = new double[tracksCount + 1];
		double[] trackTrackColumnNorms = new double[tracksCount + 1];
		
		double[] userUserColumnItemsCount = new double[usersCount + 1];
		double[] trackUserColumnItemsCount = new double[usersCount + 1];
		double[] userTrackColumnItemsCount = new double[tracksCount + 1];
		double[] trackTrackColumnItemsCount = new double[tracksCount + 1];

		// count UserUser elements
		for (int i = 0; i < userUser.getRowIds().length; i++) {
			userUserColumnNorms[userUser.getColumnIds()[i]] += userUser
					.getValues()[i];
			userUserColumnItemsCount[userUser.getColumnIds()[i]] +=1;
		}

		// count TrackTrack elements
		for (int i = 0; i < trackTrack.getRowIds().length; i++) {
			trackTrackColumnNorms[trackTrack.getColumnIds()[i]] += trackTrack
					.getValues()[i];
			trackTrackColumnItemsCount[trackTrack.getColumnIds()[i]] += 1;
		}

		for (int i = 0; i < userTrack.getRowIds().length; i++) {
			// count UserTrack elements
			userTrackColumnNorms[userTrack.getColumnIds()[i]] += userTrack
					.getValues()[i];
			userTrackColumnItemsCount[userTrack.getColumnIds()[i]] += 1;
			// count TrackUser elements
			trackUserColumnNorms[userTrack.getRowIds()[i]] += userTrack
					.getTransponedSubmatrixValues()[i];
			trackUserColumnItemsCount[userTrack.getRowIds()[i]] += 1;
		}

		// normalize UserUser elements
		for (int i = 0; i < userUser.getValues().length; i++) {
			userUser.getValues()[i] /= userUserColumnNorms[userUser.getColumnIds()[i]];
			userUser.getValues()[i] *= userUserColumnItemsCount[userUser.getColumnIds()[i]]/(userUserColumnItemsCount[userUser.getColumnIds()[i]] + trackUserColumnItemsCount[userUser.getColumnIds()[i]]);
		}

		// normalize TrackTrack elements
		for (int i = 0; i < trackTrack.getValues().length; i++) {
			trackTrack.getValues()[i] /= trackTrackColumnNorms[trackTrack
					.getColumnIds()[i]];
			trackTrack.getValues()[i] *= trackTrackColumnItemsCount[trackTrack.getColumnIds()[i]]/(trackTrackColumnItemsCount[trackTrack.getColumnIds()[i]] + userTrackColumnItemsCount[trackTrack.getColumnIds()[i]]);
		}

		// normalize UserTrack elements
		for (int i = 0; i < userTrack.getValues().length; i++) {
			userTrack.getValues()[i] /=  userTrackColumnNorms[userTrack
					.getColumnIds()[i]];
			userTrack.getValues()[i] *= userTrackColumnItemsCount[userTrack.getColumnIds()[i]]/(trackTrackColumnItemsCount[userTrack.getColumnIds()[i]] + userTrackColumnItemsCount[userTrack.getColumnIds()[i]]);
			
			userTrack.getTransponedSubmatrixValues()[i] /= trackUserColumnNorms[userTrack
					.getRowIds()[i]];
			userTrack.getTransponedSubmatrixValues()[i] *= trackUserColumnNorms[userTrack.getRowIds()[i]]/(userUserColumnItemsCount[userTrack.getRowIds()[i]] + trackUserColumnItemsCount[userTrack.getRowIds()[i]]);
		}
	}

	@Override
	public double[] randomWalkWithRestarts(PreferencesVector preferencesVector,
			double restartProbability, QuizSetVerification verification,
			int userId) {

		// restart points of RWR algorithm
		PreferencesVector restartVector = new PreferencesVector(
				preferencesVector.getUsers().clone(), preferencesVector
						.getTracks().clone(), preferencesVector.getTags()
						.clone());
		Utils.multiply(restartVector.getUsers(), restartProbability);
		Utils.multiply(restartVector.getTracks(), restartProbability);

		int step = 0;

		while (step < STEP_FOR_CONVERGENCE) {
			step++;

			double[] userVector = new double[restartVector.getUsers().length];
			double[] trackVector = new double[restartVector.getTracks().length];

			userUser.randomWalkTransition(preferencesVector.getUsers(),
					userVector, userId);
			userTrack.randomWalkTransition(preferencesVector.getTracks(),
					userVector, userId);
			userTrack.randomWalkTransitionTransposition(
					preferencesVector.getUsers(), trackVector);
			trackTrack.randomWalkTransition(preferencesVector.getTracks(),
					trackVector, -1);

			// p = (1-a)*S*p + a*q
			Utils.multiply(userVector, 1.0 - restartProbability);
			preferencesVector.setUsers(Utils.sum(restartVector.getUsers(),
					userVector));

			Utils.multiply(trackVector, 1.0 - restartProbability);
			preferencesVector.setTracks(Utils.sum(restartVector.getTracks(),
					trackVector));

			//Utils.printStepResults(preferencesVector, verification, userId, step);
			//System.out.print(Utils.getCommaSeparatedString(verification.prepareTopTracks(preferencesVector.getTracks(),100)));
		}

		return preferencesVector.getTracks();
	}
}
